package cn.wolfcode.service.impl;

import cn.wolfcode.common.exception.BusinessException;
import cn.wolfcode.common.web.CommonCodeMsg;
import cn.wolfcode.domain.AccountLog;
import cn.wolfcode.domain.AccountTransaction;
import cn.wolfcode.domain.OperateIntergralVo;
import cn.wolfcode.mapper.AccountLogMapper;
import cn.wolfcode.mapper.AccountTransactionMapper;
import cn.wolfcode.mapper.UsableIntegralMapper;
import cn.wolfcode.service.IUsableIntegralService;
import cn.wolfcode.web.msg.IntergralCodeMsg;
import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.annotation.JsonAlias;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.crypto.engines.AESLightEngine;
import org.checkerframework.checker.units.qual.A;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import sun.rmi.runtime.Log;

import java.util.Date;


@Service
@Slf4j
public class UsableIntegralServiceImpl implements IUsableIntegralService {
    @Autowired
    private UsableIntegralMapper usableIntegralMapper;
    @Autowired
    private AccountTransactionMapper accountTransactionMapper;
    @Autowired
    private AccountLogMapper accountLogMapper;

    @Override
    @Transactional
    public int descIntergral(OperateIntergralVo vo) {
        // 插入一条 变动数据的 流水记录
        AccountLog accountLog = new AccountLog();
        accountLog.setPkValue(vo.getPk());
        accountLog.setType(AccountLog.TYPE_DECR);
        accountLog.setAmount(vo.getValue());
        accountLog.setGmtTime(new Date());
        accountLog.setInfo(vo.getInfo());
        accountLogMapper.insert(accountLog);

        // 减少用户积分
        int  count =  usableIntegralMapper.descIntergral(vo.getUserId(),vo.getValue());
        if (count == 0){
            throw new BusinessException(IntergralCodeMsg.INTERGRAL_NOT_ENOUGH);
        }
        // 锁定用户积分 => 避免支付后产生异常, 导致订单显示未支付, 而客户只能看到他的钱减少了
        usableIntegralMapper.freezeIntergral(vo.getUserId(),vo.getValue());

        return count;

    }

    @Override
    public int incrIntergral(OperateIntergralVo vo) {
        // 插入一条 变动数据的 流水记录
        AccountLog accountLog = new AccountLog();
        accountLog.setPkValue(vo.getPk());
        accountLog.setType(AccountLog.TYPE_INCR);
        accountLog.setAmount(vo.getValue());
        accountLog.setGmtTime(new Date());
        accountLog.setInfo(vo.getInfo());
        accountLogMapper.insert(accountLog);

        int count = usableIntegralMapper.addIntergral(vo.getUserId(),vo.getValue());
        if (count == 0){
            throw new BusinessException(IntergralCodeMsg.OP_INTERGRAL_ERROR);
        }
        return count;
    }

    @Override
    public void incrIntergralTry(OperateIntergralVo operateIntergralVo, BusinessActionContext context) {

        /**
         *    增加积分的TCC Try 操作
         * 分析: 分布式事务异常存在
         * 1. 空回滚: try还未执行,等待时机到就执行Cancel方法
         * 2. 幂等性, 因存在网络波动,导致已经执行成功的commit 方法, 发送的消息未被 TC 获取, 此时TC过一段
         *      时间后会有一个回查机制,重新往commit那里查询执行. 那就会造成重复提交的问题
         * 3. 防悬挂: try执行到一半,等到时间到, 执行Cancel方法
         *
         * 为了避免  幂等性,防悬挂 的问题, 需要在 try 这边插入一条流水记录, 其中要设置一个状态为 try 状态
         * 后续的 commit 和 cancel 需要根据这个状态, 去判断是否存在 幂等性/防悬挂 问题
         */
        // 插入一条流水记录 account_transaction
        common(operateIntergralVo,context,AccountTransaction.STATE_TRY,AccountTransaction.TYPE_INCR);


    }

    @Override
    public void incrInergralCommit(BusinessActionContext context) {

        /**
         *    增加积分的TCC commit 操作
         * 分析: 分布式事务异常存在
         * 1. try还未执行,等待时机到就执行Cancel方法
         * 2. 幂等性, 因存在网络波动,导致已经执行成功的commit 方法, 发送的消息未被 TC 获取, 此时TC过一段
         *      时间后会有一个回查机制,重新往commit那里查询执行. 那就会造成重复提交的问题
         * 3. try执行到一般,等到时间到, 执行Cancel方法
         *
         * 则在commit 方法这里, 需要 判断account_transaction流水表中的 事务状态,
         * 1.如果数据为空, 抛出异常,
         * 2.如果是 提交 Try 状态,继续进行Confirm操作
         * 3.如果是 Commit 状态, 则运行幂等操作,即不进行任何操作 ,  如果不是Commit和Try 状态, 抛出异常
         */

        // 1. 获取 AccountTransaction流水表中的记录
        AccountTransaction accountTransaction = accountTransactionMapper.get(context.getXid() + "", context.getBranchId() + "");
        // 获取当前的 vo 对象
        OperateIntergralVo vo = JSON.parseObject(context.getActionContext("operateIntergralVo")+"", OperateIntergralVo.class);
        // 2. 如果记录为空, 则抛出异常
        if (accountTransaction == null){
            throw new BusinessException(IntergralCodeMsg.OP_INTERGRAL_ERROR);
        }
        // 3. 如果记录的状态为 初始化, 则进行 confirm 操作
        if (accountTransaction.getState().equals(AccountTransaction.STATE_TRY)){
            // 执行 confirm 操作
            // 增加积分的方法
            // 获取存入到上下文中的 vo JSON字符串,并转换为对象
            incrIntergral(vo);
            // 修改记录事务管理信息的状态为 提交 状态
            accountTransactionMapper.updateAccountTransactionState(context.getXid()+"",context.getBranchId()+"",AccountTransaction.STATE_COMMIT,AccountTransaction.STATE_TRY);

        }else if (accountTransaction.getState().equals(AccountTransaction.STATE_COMMIT)){
            // 如果记录的状态是 Commit 状态, 则为了保证幂等性, 不做任何操作
        }else {
            // 说明此事是Cancel或者其他状态,直接抛异常
            throw new BusinessException(IntergralCodeMsg.OP_INTERGRAL_ERROR);
        }

    }

    @Override
    public void incrIntergralRollback(BusinessActionContext context) {
        /**
         *    增加积分的TCC rollback回滚 操作
         * 分析: 分布式事务异常存在
         * 1. try还未执行,等待时机到就执行Cancel方法
         * 2. 幂等性, 因存在网络波动,导致已经执行成功的commit 方法, 发送的消息未被 TC 获取, 此时TC过一段
         *      时间后会有一个回查机制,重新往commit那里查询执行. 那就会造成重复提交的问题
         * 3. try执行到一般,等到时间到, 执行Cancel方法
         *
         * 则在 rollback回滚 方法这里, 需要 判断account_transaction流水表中的 事务状态,
         * 1.如果流水数据(事务控制记录)为空, 说明此时属于 空回滚异常, 则创建并插入 accountTransaction流水记录,状态为 Cancel
         *      !!!!若插入失败, 抛出异常,等待重试
         * 2. 事务控制表数据不为空
         *       状态是否为 try 即初始化, 若是, 继续执行 Cancel
         *                               若不是,状态是否为 回滚  => 是,运行幂等, 否, 抛出异常
         *                                      (这是是由于cancel因网络波动导致消息未被TC接收)
         */


        AccountTransaction accountTransaction = accountTransactionMapper.get(context.getXid()+"", context.getBranchId() + "");
        OperateIntergralVo vo = JSON.parseObject(context.getActionContext("operateIntergralVo").toString(), OperateIntergralVo.class);

        if (accountTransaction == null){
            // 获取存在上下文中的 vo 对象, 对accountTransaction 事务控制表流水记录 设置属性值,并插入数据库
            common(vo,context,AccountTransaction.STATE_CANCEL,AccountTransaction.TYPE_INCR);
            throw new BusinessException(CommonCodeMsg.ILLEGAL_OPERATION);
        }

        if (AccountTransaction.STATE_TRY.equals(accountTransaction.getState())){
            // 正常执行 cancel 将 记录事务控制的状态 设置为 cencel
            accountTransactionMapper.updateAccountTransactionState(context.getXid()+"",context.getBranchId()+"",AccountTransaction.STATE_CANCEL,AccountTransaction.STATE_TRY);
        }else if (accountTransaction.getState().equals(AccountTransaction.STATE_CANCEL)){
            log.info("本来就是 回滚状态, 只是消息因为网络波动,没有被TC接受,导致TC重试, 因而不操作即可");
        }

    }


    @Override
    public void descIntergralTry(OperateIntergralVo operateIntergralVo, BusinessActionContext context) {
        /**
         * 支付 => 减钱操作,将支付的钱锁住, 如果没有异常, 在commit方法再解锁
          */
        log.info("进入支付的减少积分try方法了...");
        // 插入 事务控制 记录信息
        common(operateIntergralVo,context,AccountTransaction.STATE_TRY,AccountTransaction.TYPE_DECR);
        // 创建account_log的流水记录信息, 并锁定用户的支付积分,减少用户金额
        // => 进入commit方法将冻结积分释放,  如果产生异常,cancel方法则回补积分

        // 冻结用户积分 => 减真实金额在commit方法中进行
        int count = usableIntegralMapper.freezeIntergral(operateIntergralVo.getUserId(), operateIntergralVo.getValue());
        if (count == 0){
            throw new BusinessException(IntergralCodeMsg.INTERGRAL_NOT_ENOUGH);
        }
    }

    @Override
    public void descIntergralCommit(BusinessActionContext context) {
        log.info("进入支付的减少积分commit方法了...");
        // 获取本次操作的事务控制流水信息
        AccountTransaction accountTransaction = accountTransactionMapper.get(context.getXid()+"", context.getBranchId() + "");
        if (accountTransaction == null){
            throw new BusinessException(CommonCodeMsg.ILLEGAL_OPERATION);
        }

        // 获取存放在 context 上下文中 vo 对象
        OperateIntergralVo vo = JSON.parseObject(context.getActionContext("operateIntergralVo") + "", OperateIntergralVo.class);
        // 防止幂等性操作, 先判断此刻的 accountTrasaction表中的 state 是否为 try状态, 是的话执行业务方法
        // 若为 commit 状态, 说明是因为网络波动导致 TC 没有接收到消息 而 进行回调Commit 直接不操作即可
        if (AccountTransaction.STATE_TRY.equals(accountTransaction.getState())){
            // try 状态
            // 插入变动的日志信息
            insertAccountLog(vo,AccountLog.TYPE_DECR);
            //   commit => 说明没有异常产生,释放积分 / 提交改变，冻结金额真实扣除
            usableIntegralMapper.commitChange(vo.getUserId(),vo.getValue());
            // 修改状态为已提交状态
            int count = accountTransactionMapper.updateAccountTransactionState(context.getXid(),context.getBranchId()+"",
                    AccountTransaction.STATE_COMMIT,AccountTransaction.STATE_TRY);
            if(count==0){
                //把事务状态从try变成cancel阶段失败,通知管理员，处理问题
                throw new BusinessException(IntergralCodeMsg.OP_INTERGRAL_ERROR);
            }

        }else if (AccountTransaction.STATE_COMMIT.equals(accountTransaction.getState())){

        }else {
            throw new BusinessException(CommonCodeMsg.ILLEGAL_OPERATION);
        }

    }

    @Override
    public void descTntergralRollback(BusinessActionContext context) {
        log.info("进入到执行 rollback 方法...");
        // 空回滚: try 未执行, cancel 先执行 => 判断是否有 accountTransationcal对象, 没有的话创建一个,状态为Cancel
        // 幂等性: cancel 消息没有被TC接收, TC重试 => 先判断此时的状态是否为Cancel, 是的话直接不操作
        // 防悬挂: try 执行一半, 时间片度到, 直接执行cancel => 判断此时的状态是否为 try, 是的话修改状态

        // 获取vo对象
        OperateIntergralVo vo = JSON.parseObject(context.getActionContext("operateIntergralVo") + "", OperateIntergralVo.class);
        // 获取 AccountTransaction 记录事务控制的信息
        AccountTransaction accountTransaction = accountTransactionMapper.get(context.getXid(), context.getBranchId() + "");
        if (accountTransaction == null){
            // 说明try方法还没有执行，空回滚，需要插入一条事务记录,避免try方法执行.
            common(vo,context,AccountTransaction.STATE_CANCEL,AccountTransaction.TYPE_DECR);
            return;
        }
        if (AccountTransaction.STATE_TRY.equals(accountTransaction.getState())){
            // 产生异常, 将冻结金额回补,用户积分返还
            // 修改状态
            int count = accountTransactionMapper.updateAccountTransactionState(context.getXid(),context.getBranchId()+"",
                    AccountTransaction.STATE_CANCEL,AccountTransaction.TYPE_DECR);
            if(count==0){
                //把事务状态从try变成commit阶段失败,通知管理员，处理问题
                throw new BusinessException(IntergralCodeMsg.OP_INTERGRAL_ERROR);
            }
            usableIntegralMapper.unFreezeIntergral(vo.getUserId(),vo.getValue());
        }else if (AccountTransaction.STATE_COMMIT.equals(accountTransaction.getState())) {

        }else {
            throw new BusinessException(IntergralCodeMsg.OP_INTERGRAL_ERROR);
        }




    }

    private void insertAccountLog(OperateIntergralVo operateIntergralVo,int type) {
        AccountLog log = new AccountLog();
        log.setPkValue(operateIntergralVo.getPk());
        log.setAmount(operateIntergralVo.getValue());
        log.setGmtTime(new Date());
        log.setType(type);
        accountLogMapper.insert(log);
    }
    public void common(OperateIntergralVo vo,BusinessActionContext context, Integer state,Integer type){
        AccountTransaction accountTransaction = new AccountTransaction();
        accountTransaction.setActionId(context.getBranchId() + "");
        accountTransaction.setAmount(vo.getValue());
        accountTransaction.setGmtCreated(new Date());
        accountTransaction.setState(state);
        accountTransaction.setTxId(context.getXid());
        accountTransaction.setGmtModified(new Date());
        accountTransaction.setType(type.toString());
        accountTransaction.setUserId(vo.getUserId());
        accountTransactionMapper.insert(accountTransaction);
    }
}
